<!DOCTYPE html>
<meta charset="utf-8">
<title>Popover combined with dialog/fullscreen behavior</title>
<link rel=author href="mailto:masonf@chromium.org">
<link rel=help href="https://open-ui.org/components/popover.research.explainer">
<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/common/top-layer.js"></script>
<script src="resources/popover-utils.js"></script>

<button id=visible>Visible button</button>
<div id=examples>
  <dialog popover>Popover Dialog</dialog>
  <dialog popover open style="top:50px;">Open Non-modal Popover Dialog</dialog>
  <div    popover class=fullscreen>Fullscreen Popover</div>
  <dialog popover class=fullscreen>Fullscreen Popover Dialog</dialog>
  <dialog popover open class=fullscreen style="top:200px;">Fullscreen Open Non-modal Popover Dialog</dialog>
</div>

<style>
  [popover] {
    inset:auto;
    top:0;
    left:0;
  }
  [popover].fullscreen.visible {
    display:block;
  }
</style>

<script>
const isDialog = (ex) => ex instanceof HTMLDialogElement;
const isFullscreen = (ex) => ex.classList.contains('fullscreen');
function ensureIsOpenPopover(ex,message) {
  // Because :popover-open will eventually support <dialog>, this does extra work to
  // verify we're dealing with an :popover-open Popover. Note that this will also throw
  // if this is an element with the `popover` attribute that has been made
  // visible via an explicit `display:block` style rule.
  message = message || 'Error';
  assert_true(ex.matches(':popover-open'),`${message}: Popover doesn\'t match :popover-open`);
  ex.hidePopover(); // Shouldn't throw if this is a showing popover
  ex.showPopover(); // Show it again to avoid state change
  assert_true(ex.matches(':popover-open'),`${message}: Sanity`);
}
window.onload = () => requestAnimationFrame(() => requestAnimationFrame(() => {
  const examples = Array.from(document.querySelectorAll('#examples>*'));
  examples.forEach(ex => {
    promise_test(async (t) => {
      t.add_cleanup(() => ex.remove());
      // Test initial conditions
      if (ex.hasAttribute('open')) {
        assert_true(isDialog(ex));
        assert_true(isElementVisible(ex),'Open dialog should be visible by default');
        ex.showPopover(); // Should not throw
        ex.removeAttribute('open');
        ex.hidePopover();
        assert_false(isElementVisible(ex),'Removing the open attribute should hide the dialog');
      } else {
        ex.showPopover(); // Should not throw
        ensureIsOpenPopover(ex,'showPopover should work');
        ex.hidePopover(); // Should not throw
        assert_false(ex.matches(':popover-open'),'hidePopover should work');
      }
      assert_false(isElementVisible(ex));

      // Start with popover, try the other API
      ex.showPopover();
      ensureIsOpenPopover(ex);
      let tested_something=false;
      if (isDialog(ex)) {
        tested_something=true;
        assert_throws_dom("InvalidStateError",() => ex.showModal(),'Calling showModal() on an already-showing Popover should throw InvalidStateError');
        ex.show(); // Should not throw
        ex.close();
        ex.showPopover();
      }
      if (isFullscreen(ex)) {
        tested_something=true;
        let requestSucceeded = false;
        await blessTopLayer(ex);
        await ex.requestFullscreen()
          .then(() => {requestSucceeded = true;}) // We should not hit this.
          .catch((exception) => {
            // This exception is expected.
            assert_equals(exception.name,'TypeError',`Invalid exception from requestFullscreen() (${exception.message})`);
          });
        assert_false(requestSucceeded,'requestFullscreen() should not succeed when the element is an already-showing Popover');
      }
      assert_true(tested_something);
      ensureIsOpenPopover(ex);
      ex.hidePopover();

      // Start with the other API, then try popover
      if (isDialog(ex)) {
        ex.show();
        assert_true(ex.hasAttribute('open'));
        ex.showPopover(); // Should not throw
        ex.close();
        assert_false(ex.hasAttribute('open'));
        ex.hidePopover();
        ex.showModal();
        assert_true(ex.hasAttribute('open'));
        assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-showing modal dialog should throw InvalidStateError');
        ex.close();
        assert_false(ex.hasAttribute('open'));
        ex.hidePopover();
      } else if (isFullscreen(ex)) {
        let requestSucceeded = false;
        await blessTopLayer(visible);
        await ex.requestFullscreen()
          .then(() => {
            assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-fullscreen element should throw InvalidStateError');
          });
        await document.exitFullscreen()
        .then(() => assert_true(true));
      }

      // Finally, try invoking these combined popovers via a declarative invoker
      const button = document.createElement('button');
      t.add_cleanup(() => button.remove());
      document.body.appendChild(button);
      button.popoverTargetElement = ex;
      button.popoverTargetAction = "toggle";
      assert_false(ex.matches(':popover-open'));
      await clickOn(button);
      ensureIsOpenPopover(ex,'Invoking element should be able to invoke all popovers');
      ex.hidePopover();
      if (isDialog(ex)) {
        ex.showModal();
        assert_true(ex.hasAttribute('open'));
      } else if (isFullscreen(ex)) {
        // Popover fullscreen isn't visible by default, so explicitly add
        // display:block, so that calls to "clickOn" can succeed.
        ex.classList.add('visible');
        await blessTopLayer(ex);
        await ex.requestFullscreen();
      } else {
        assert_unreached('Not a dialog or fullscreen');
      }
      ex.appendChild(button); // Add button to the element, so it's visible to click
      await clickOn(button);
      assert_false(ex.matches(':popover-open'),'The invoker click should have failed on the already-open dialog/fullscreen');
      if (isDialog(ex)) {
        ex.close();
      } else {
        await document.exitFullscreen()
      }
    }, `Popover combination: ${ex.textContent}`);
  });
}));
</script>
